﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StyleCopFileScavenger.cs" company="StyleCop for ReSharper Development Team">
//   Copyright (c) StyleCop for ReSharper Development Team. All rights reserved.
// </copyright>
// <summary>
//   Defines the StyleCopFileScavenger type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

extern alias JB;

namespace StyleCop.For.ReSharper.Core
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;

    using JetBrains.ProjectModel;
    using JetBrains.ReSharper.Psi;

    #endregion

    /// <summary>
    /// Manages the scaveging of temporary files used by SCfR#.
    /// </summary>
    public class StyleCopFileScavenger
    {
        #region Constants and Fields

        /// <summary>
        /// Timeout (in seconds) that we should clean down the Shadow Copy folders and files.
        /// </summary>
        private const int ShadowCopyScavengeTimoutPeriod = 60;

        /// <summary>
        /// Object that can be used to synchronize access to the Shadow Files.
        /// </summary>
        private static readonly object syncRoot = new object();

        /// <summary>
        /// Used to count down to the next shadow file clean up. 
        /// </summary>
        private readonly Stopwatch scavengingStopwatch;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the StyleCopFileScavenger class.
        /// </summary>
        /// <param name="scavengingStopwatch">
        /// Global static stopwatch which regulates scavenging.
        /// </param>
        public StyleCopFileScavenger(Stopwatch scavengingStopwatch)
        {
            this.scavengingStopwatch = scavengingStopwatch;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a shadow copy of the file currently being processed within the IDE.
        /// </summary>
        /// <remarks>
        /// A shadow copy is taken to keep the IDE highlights in sync with the violations encountered,
        /// as Microsoft StyleCop only executes against a file, the shadow copy is required.
        /// </remarks>
        /// <param name="file">
        /// File to analyze.
        /// </param>
        /// <returns>
        /// A List of the shadow copied files.
        /// </returns>
        public List<string> CreateShadowCopy(IProjectFile file)
        {
            var shadowCopyList = new List<string>();

            lock (syncRoot)
            {
                try
                {
                    var files = GetAllFilesForFile(file);

                    shadowCopyList = new List<string>(files.Count);

                    foreach (var projectFile in files)
                    {
                        //here we could generate a hash of hte directory
                        var fileInfo = FileSystemOperations.GetShadowFilePathForFile(projectFile);

                        FileSystemOperations.CreateDirectory(fileInfo.DirectoryName);

                        if (!FileSystemOperations.DirectoryExists(fileInfo.DirectoryName))
                        {
                            throw new IOException("Directory does not exist : " + fileInfo.DirectoryName);
                        }

                        var psiFile = PsiManager.GetInstance(file.GetSolution()).GetPsiFile(projectFile, PsiLanguageType.GetByProjectFile(projectFile));

                        if (psiFile != null)
                        {
                            if (FileSystemOperations.CreateFileAndWriteText(fileInfo.FullName, psiFile.GetText()))
                            {
                                shadowCopyList.Add(fileInfo.FullName);
                            }
                        }
                    }
                }
                catch (IOException exception)
                {
                    JB::JetBrains.Util.Logger.LogException(exception);
                }
                catch (UnauthorizedAccessException exception)
                {
                    JB::JetBrains.Util.Logger.LogException(exception);
                }
            }

            return shadowCopyList;
        }


        /// <summary>
        /// Periodically clears down the Shadow Folders and temporary files.
        /// </summary>
        public void ScavengeShadowCopies()
        {
            // Only clean up the cache every 30 seconds or so.
            if (this.scavengingStopwatch.Elapsed > new TimeSpan(0, 0, 0, ShadowCopyScavengeTimoutPeriod))
            {
                DeleteShadowCopy();

                this.scavengingStopwatch.Reset();
                this.scavengingStopwatch.Start();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Deletes the shadow copy files and folder structure.
        /// </summary>
        private static void DeleteShadowCopy()
        {
            lock (syncRoot)
            {
                try
                {
                    FileSystemOperations.PurgeShadowFolder();
                }
                catch
                {
                    return;
                }
            }
        }

        /// <summary>
        /// The passed in file is always index 0 in the returned IList.
        /// </summary>
        /// <param name="projectFile"></param>
        /// <returns></returns>
        private static IList<IProjectFile> GetAllFilesForFile(IProjectFile projectFile)
        {
            var rootDependsItem = projectFile.GetDependsUponItemForItem();

            if (rootDependsItem == null)
            {
                return new List<IProjectFile> { projectFile };
            }

            var dependentFiles = rootDependsItem.GetDependentFiles();

            var list = new List<IProjectFile> { projectFile };

            list.AddRange(dependentFiles.Where(file => !file.Equals(projectFile)));

            return list;
        }

        #endregion
    }
}